package org.framework.lazy.cloud.network.heartbeat.protocol.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import lombok.extern.slf4j.Slf4j;
import org.framework.lazy.cloud.network.heartbeat.common.adapter.ChannelTypeAdapter;
import org.framework.lazy.cloud.network.heartbeat.common.advanced.payload.NettyProxyMsg;
import org.framework.lazy.cloud.network.heartbeat.common.constant.ProxyMessageType;
import org.framework.lazy.cloud.network.heartbeat.common.utils.ChannelAttributeKeyUtils;
import org.framework.lazy.cloud.network.heartbeat.protocol.route.ClientProxyRoute;
import org.framework.lazy.cloud.network.heartbeat.protocol.route.ProxyRoute;
import org.framework.lazy.cloud.network.heartbeat.protocol.route.RouteContext;
import org.framework.lazy.cloud.network.heartbeat.protocol.route.RouteType;
import org.framework.lazy.cloud.network.heartbeat.protocol.utils.FullHttpRequestUtils;
import org.wu.framework.core.NormalUsedString;
import org.wu.framework.core.utils.ObjectUtils;

import java.net.URI;
import java.util.UUID;

/**
 * description 服务端数据处理器
 *
 * @author 吴佳伟
 * @date 2023/09/13 10:27
 */
@Slf4j
public class NettyHttpProxyHandler extends ChannelInboundHandlerAdapter {

    private final ChannelTypeAdapter channelTypeAdapter;


    public NettyHttpProxyHandler(ChannelTypeAdapter channelTypeAdapter) {
        this.channelTypeAdapter = channelTypeAdapter;
    }

    static void closeOnFlush(Channel ch) {
        if (ch.isActive()) {
            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelActive()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     * <p>
     * Sub-classes may override this method to change behavior.
     *
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        if (channel instanceof NioSocketChannel) {
            System.out.println("http 这是一个TCP通道");
        } else if (channel instanceof NioDatagramChannel) {
            System.out.println("http 这是一个UDP通道");
        } else {
            System.out.println("http 未知类型的通道");
        }
        String visitorId = UUID.randomUUID().toString();
        ChannelAttributeKeyUtils.buildVisitorId(channel, visitorId);
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest request) {
            URI uri = new URI(request.uri());
            String originHost = uri.getHost();
            int originPort = uri.getPort();
            if (originPort == -1) {
                originPort = 80;
            }

            ByteBuf byteBuf = FullHttpRequestUtils.toByteBuf(request);
            byte[] bytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bytes);


            String visitorId = ChannelAttributeKeyUtils.getVisitorId(ctx.channel());
            NettyProxyMsg proxyMsg = new NettyProxyMsg();


            ProxyRoute route = RouteContext.getRoute(originHost, String.valueOf(originPort));
            proxyMsg.setVisitorId(visitorId);
            proxyMsg.setData(bytes);

            if (ObjectUtils.isEmpty(route)) {
                // 本地代理
                proxyMsg.setType(ProxyMessageType.HTTP_LOCAL_PROXY);
                proxyMsg.setTargetIp(originHost);
                proxyMsg.setTargetPort(originPort);
            } else {
                // 远程代理
                String targetIp = route.getTargetIp();
                String targetPort = NormalUsedString.ASTERISK.equals(route.getTargetPort()) ? String.valueOf(originPort) : route.getTargetPort();
                proxyMsg.setTargetIp(targetIp);
                proxyMsg.setTargetPort(Integer.valueOf(targetPort));
                if (RouteType.LOCAL.equals(route.getRouteType())) {
                    proxyMsg.setType(ProxyMessageType.HTTP_LOCAL_PROXY);
                } else if (RouteType.CLIENT_PROXY_CLIENT.equals(route.getRouteType())) {
                    ClientProxyRoute clientProxyRoute = (ClientProxyRoute) route;
                    String clientId = clientProxyRoute.getTargetClientId();
                    proxyMsg.setClientId(clientId);
                    proxyMsg.setType(ProxyMessageType.HTTP_CLIENT_PROXY_CLIENT_);
                } else if (RouteType.CLIENT_PROXY_SEVER.equals(route.getRouteType())) {
                    proxyMsg.setType(ProxyMessageType.HTTP_CLIENT_PROXY_SERVER_);
                } else if (RouteType.SERVER_PROXY_CLIENT.equals(route.getRouteType())) {
                    ClientProxyRoute clientProxyRoute = (ClientProxyRoute) route;
                    String clientId = clientProxyRoute.getTargetClientId();
                    proxyMsg.setClientId(clientId);
                    proxyMsg.setType(ProxyMessageType.HTTP_SERVER_PROXY_CLIENT_);
                }
            }
            channelTypeAdapter.handler(ctx, proxyMsg);
            // 判断是否被路由

        } else {
            log.warn("this is not http request");
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel nextChannel = ChannelAttributeKeyUtils.getNextChannel(ctx.channel());
        if (nextChannel != null) {
            String visitorId = ChannelAttributeKeyUtils.getVisitorId(ctx.channel());
            // close
            NettyProxyMsg closeMes = new NettyProxyMsg();
            closeMes.setVisitorId(visitorId);
            closeMes.setType(ProxyMessageType.HTTP_REPORT_CLIENT_PROXY_SERVER_TRANSFER_CLOSE_);
            nextChannel.writeAndFlush(closeMes);
            closeOnFlush(nextChannel);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        closeOnFlush(ctx.channel());
    }
}